package react.debounce;

import lombok.Getter;
import lombok.Setter;
import org.apache.commons.collections4.MapUtils;


import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author shenenlu 2021年06月18日 下午17:16:14
 */
//@Slf4j
public class SimpleDebounceCallable<T> {


    final private static String DEFAULT_KEY = "__default";
    final private static String REMOVE_TIMEOUT_KEY = "__clear_time_out";
    // default timeout is 10 minutes
    final private static long DEFAULT_TIMEOUT = 60000 * 10;

    final private Map<String, Long> lastTickTimeMap = new ConcurrentHashMap<>();

    @Setter
    @Getter
    private long timeout = 0;


    public SimpleDebounceCallable() {
        this(DEFAULT_TIMEOUT);
    }

    public SimpleDebounceCallable(long timeout) {
        this.timeout = timeout;
    }

    public int tick() {
        return replaceIfTimeout(DEFAULT_KEY) ? 1 : 0;
    }

    public int tick(String key) {
        return replaceIfTimeout(key) ? 1 : 0;
    }

    private boolean replaceIfTimeout(String key) {
        boolean replace = false;
        synchronized (lastTickTimeMap) {
            if (isExpiredValue(lastTickTimeMap.get(key))) {
                lastTickTimeMap.put(key, System.currentTimeMillis());
                replace = true;
            }
        }
        return replace;
    }

    private boolean isExpiredValue(Long val) {
        return val == null || Math.abs(System.currentTimeMillis() - val) >= timeout;
    }

    private void removeTimeoutItems() {
        if (MapUtils.isEmpty(lastTickTimeMap)) {
            return;
        }
        synchronized (lastTickTimeMap) {
            lastTickTimeMap.entrySet().removeIf(stringLongEntry -> isExpiredValue(stringLongEntry.getValue()));
        }
    }

    /**
     * debounce to execute specified lambda function
     *
     * @param callable lambda function to be call if available
     * @return Optional<T>
     */
    public Optional<T> debounce(Callable<T> callable) {
        try {
            if (replaceIfTimeout(REMOVE_TIMEOUT_KEY)) {
                removeTimeoutItems();
            }
            return Optional.ofNullable(callable.call());
        } catch (Exception e) {
//            log.error("debounce", e);
        }
        return Optional.empty();
    }

    public Optional<T> debounce(Callable<T> callable, String key) {
        return replaceIfTimeout(key) ? debounce(callable) : Optional.empty();
    }


    public static void main(String[] args) {

        //2.1 自动debounce抑制
        // 定义成员表变量或者静态变量, 设置抑制时间 5秒
        final SimpleDebounceCallable<Integer> debounce = new SimpleDebounceCallable<>(1000 * 5);

        String key = "hello-call";
        Optional<Integer> ret = debounce.debounce(() -> {
            int c = 0;
//            (处理代码)....

            return c;
        }, key);


        //2.2 主动debounce抑制
        // 定义成员表变量或者静态变量, 设置抑制时间 5秒
       final SimpleDebounceCallable<Integer> debounce2 = new SimpleDebounceCallable<>(1000 * 5);

        if (debounce.tick(key) > 0) {
            Optional<Integer> ret1 = debounce.debounce(() -> {
                int c = 0;
//                (处理代码)....

                return c;
            });
        }
    }

}
